#if !defined(UTF8_CONVERT_H)
#define UTF8_CONVERT_H

#include <stdio.h>
#include <string>

class Utf8
{
private:
    typedef unsigned short ucs2_t;	/* Unicode character [D5] */
    typedef unsigned int   ucs4_t;	/* Unicode scalar character [D28] */
    typedef ucs4_t    ucs_t;

    const static int UCS_CHAR_INVALID = 0xFFFE;
    const static int UCS_CHAR_NONE = 0xFFFF;

public:
	static bool ConvertFromUcs(const std::wstring& in, std::string& out) {
        return ConvertFromUcs(in.c_str(), (int)in.size(), out);
    }
    static bool ConvertFromUcs(const wchar_t* in, std::string& out) {
        return in && ConvertFromUcs(in, wcslen(in), out);
    }

    static bool ConvertFromUcs(const wchar_t* in, int size, std::string& out) {
        if (NULL == in) return false;
		static const int BUF_SIZE = 32;

		unsigned char outbuf[BUF_SIZE];
		memset(outbuf, 0, BUF_SIZE);

		out.clear();
        out.reserve(size << 1);
        for(int i = 0; i < size; ++i) {
            ucs_t input = (ucs_t)in[i];
            size_t ret = convert_from_ucs(input, (unsigned char*)outbuf);

            if (ret < 1) {
                out.clear();
                return false;
            }
			
            out.append((const char*)outbuf, ret);
        }
        return true;
    }

    static bool ConvertFromUcs(const wchar_t* in, unsigned char *outbuf, size_t bufSize) {
        return in && ConvertFromUcs(in, wcslen(in), outbuf, bufSize);
    }
    static bool ConvertFromUcs(const wchar_t* in, int size, unsigned char *outbuf, size_t bufSize) {
        if (in == NULL || outbuf == NULL) return false;

        unsigned char *out = outbuf;
        size_t outbyteleft = bufSize;
		
        for(int i = 0; i < size; ++i) {
            ucs_t input = (ucs_t)in[i];
            size_t ret = convert_from_ucs(input, &out, outbyteleft);

            if (ret < 1) return false;
        }
        return true;
    }

	static bool ConvertToUcs(const std::string& in, std::wstring& out) {
        return ConvertToUcs(in.c_str(), out);
    }
	static bool ConvertToUcs(const char* in, std::wstring& out) {
        return in && ConvertToUcs(in, strlen(in), out);
    }
	static bool ConvertToUcs(const char* in, size_t size, std::wstring& out) {
        const unsigned char *inBuf = (const unsigned char *)in;
        size_t inSize = size;
        int cnt = 0;

		if (NULL == in) return false;

        while (inSize > 0) {
            ucs_t byte = convert_to_ucs(&inBuf, inSize);

            if (byte == UCS_CHAR_INVALID) return false;
            if (byte == UCS_CHAR_NONE) break;

            out.push_back((wchar_t)byte);
        }

        return true;
    }

    
    static bool ConvertToUcs(const char* in, wchar_t* out, int size) {
        return in && ConvertToUcs((const unsigned char*)in, strlen(in), out, size);
    }

    static bool ConvertToUcs(const unsigned char *inbuf, size_t bufSize, wchar_t* out, int size) {
        if (inbuf == NULL || bufSize <= 0 || out == NULL || size < 2) return false;

        const unsigned char *in = inbuf;
        size_t inSize = bufSize;
        int cnt = 0;

        while (inSize > 0) {
            ucs_t byte = convert_to_ucs(&in, inSize);

            ++cnt;
            if (byte == UCS_CHAR_INVALID || cnt >= size) return false;
            if (byte == UCS_CHAR_NONE) break;

            out[cnt-1] = (wchar_t)byte;
        }

        return true;
    }


private:
    #define cont_byte(b) (((b) & 0x3F) | 0x80)
    static size_t convert_from_ucs(ucs_t in, unsigned char ** outbuf, size_t& outbytesleft) {
        unsigned char *cp;
        size_t n;

        if (in == UCS_CHAR_NONE) return 1;	// No state reinitialization for table charsets
        if (in < 0x80) { n = 1;
        } else if (in < 0x800) { n = 2;
        } else if (in < 0x10000) { n = 3;
        } else if (in < 0x110000) {  n = 4;
        } else { return -1; }

        if (outbytesleft < n) return 0;
        cp = *outbuf;
        switch (n) {
        case 1:
            *cp = (unsigned char)in;
            break;
        case 2:
            *cp++ = (unsigned char)((in >> 6) | 0xC0);
            *cp++ = (unsigned char)cont_byte(in);
            break;
        case 3:
            *cp++ = (unsigned char)((in >> 12) | 0xE0);
            *cp++ = (unsigned char)cont_byte(in >> 6);
            *cp++ = (unsigned char)cont_byte(in);
            break;
        case 4:
            *cp++ = (unsigned char)((in >> 18) | 0xF0);
            *cp++ = (unsigned char)cont_byte(in >> 12);
            *cp++ = (unsigned char)cont_byte(in >> 6);
            *cp++ = (unsigned char)cont_byte(in);
            break;
        }
        outbytesleft -= n;
        *outbuf += n;
        return 1;
    }

	static size_t convert_from_ucs(ucs_t in, unsigned char* outbuf) {
		unsigned char *cp;
		size_t n;

		if (in == UCS_CHAR_NONE) return 1;	// No state reinitialization for table charsets
		if (in < 0x80) { n = 1;
		} else if (in < 0x800) { n = 2;
		} else if (in < 0x10000) { n = 3;
		} else if (in < 0x110000) {  n = 4;
		} else { return -1; }

		cp = outbuf;
		switch (n) {
		case 1:
			*cp = (unsigned char)in;
			break;
		case 2:
			*cp++ = (unsigned char)((in >> 6) | 0xC0);
			*cp++ = (unsigned char)cont_byte(in);
			break;
		case 3:
			*cp++ = (unsigned char)((in >> 12) | 0xE0);
			*cp++ = (unsigned char)cont_byte(in >> 6);
			*cp++ = (unsigned char)cont_byte(in);
			break;
		case 4:
			*cp++ = (unsigned char)((in >> 18) | 0xF0);
			*cp++ = (unsigned char)cont_byte(in >> 12);
			*cp++ = (unsigned char)cont_byte(in >> 6);
			*cp++ = (unsigned char)cont_byte(in);
			break;
		}
		return n;
	}

    static ucs_t convert_to_ucs(const unsigned char** inbuf, size_t& inbytesleft)
    {
        const unsigned char *in = *inbuf;
        unsigned char byte = *in++;
        ucs_t res = byte;

        if (byte >= 0xC0) {
            if (byte < 0xE0) {
                if (inbytesleft < 2) {
                    return UCS_CHAR_NONE;
                }
                res = (*in & 0xC0) == 0x80 ? 
                    ((byte & 0x1F) << 6) | (*in++ & 0x3F) : UCS_CHAR_INVALID;
            }
            else if (byte < 0xF0) {
                if (inbytesleft < 3) return UCS_CHAR_NONE;
                if (((in[0] & 0xC0) == 0x80) && ((in[1] & 0xC0) == 0x80)) {
                    res = ((byte & 0x0F) << 12) | ((in[0] & 0x3F) << 6)
                        | (in[1] & 0x3F);
                    in += 2;
                } 
                else {
                    res = UCS_CHAR_INVALID;
                }
            } else if (byte <= 0xF4) {
                if (inbytesleft < 4) return UCS_CHAR_NONE;
                if (((byte == 0xF4 && ((in[0] & 0xF0) == 0x80))
                    || ((in[0] & 0xC0) == 0x80))
                    && ((in[1] & 0xC0) == 0x80)
                    && ((in[2] & 0xC0) == 0x80)) {
                        res = ((byte & 0x7) << 18) | ((in[0] & 0x3F) << 12)
                            | ((in[1] & 0x3F) << 6) | (in[2] & 0x3F);
                        in += 3;
                } else {
                    res = UCS_CHAR_INVALID;
                }
            }
            else {
                res = UCS_CHAR_INVALID;
            }
        }
        else if (byte & 0x80) {
            res = UCS_CHAR_INVALID;
        }

        inbytesleft -= (in - *inbuf);
        *inbuf = in;
        return res;
    }
};

#endif //#if !defined(UTF8_CONVERT_H)